# Copyright (c) 2021 Presto Labs Pte. Ltd.
# Author: chunhui

import glob
import json
import re
import datetime
import warnings

from absl import app, flags
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.exchange.kr_rest.product.product_impl import generate_product_from_str2
from coin.base.query_util import \
    query_exchange_rates

FLAGS = flags.FLAGS


DECOMM_HISTORY_JSON_PATH = '/remote/iosg/home-3/teamy/ops/decomm_history/decomm_history.json'


def calculate_stickness(symbol, mea, symbol_prices, delta=0.1):
  product_info = json.load(open(f'data/coin2/product_info/{mea}.json', 'r'))
  tick_price = None
  for p in product_info['product_infos']:
    if p['symbol'] == symbol:
      tick_price = p['price_ticksize'][0]['value']
      break
  if not tick_price:
    warnings.warn(f'No product info for {symbol}')
    return
  base = symbol.split('-')[0].replace('1000', '')
  tsymbol = symbol.split('.')[0].replace('-', '').replace('1000', '').replace('BCHN', 'BCH')
  if tsymbol not in symbol_prices:
    price = query_exchange_rates([base], 'USD')[0]
  else:
    price = symbol_prices[tsymbol]
  if '1000' in symbol:
    price *= 1e3
  tick_price_bp = tick_price / price * 1e4
  return max(2, tick_price_bp * (1 + delta))


def write_decomm_info(mea, symbol):
  mark_date = \
      (datetime.date.today() + datetime.timedelta(days=FLAGS.cooldown_days)).strftime('%Y%m%d')
  with open(DECOMM_HISTORY_JSON_PATH) as f:
    data = json.load(f)
  mea_history = data.setdefault(mea, {})
  mea_history[symbol] = mark_date
  with open(DECOMM_HISTORY_JSON_PATH, 'w') as f:
    json.dump(data, f, indent=2)


def read_binance_prices():
  files = sorted(glob.glob('/remote/iosg/home-3/yuxuan/shared/prices/binance_price*.json'))
  assert len(files) > 0
  with open(files[-1]) as fi:
    data = json.load(fi)
  return {e['symbol']: float(e['price']) for e in data}


def get_remaining_symbols(config, strategy_name):
  ref_symbols = []
  trade_symbols = []
  for _, product in config['products'].items():
    if 'basis_spread' in strategy_name or 'mm_currency' in strategy_name:
      s0 = product['symbol'].split(':')[-1]
      s1 = product['hedge']['symbol'].split(':')[-1]
      ref_symbols += [s0, s1]
      trade_symbols += [s0, s1]
    elif 'mm_portfolio' in strategy_name:
      s0 = product['symbol'].split(':')[-1]
      s1 = product['hedge']['symbol'].split(':')[-1]
      ref_symbols += [s0, s1]
      trade_symbols.append(s0)
    elif 'mmrev' in strategy_name:
      s = product['trade_symbol'].split(':')[-1]
      ref_symbols.append(s)
      trade_symbols.append(s)
    elif 'basis_smm2' in strategy_name:
      s0 = product['ref_symbol'].split(':')[-1]
      s1 = product['trade_symbol'].split(':')[-1]
      ref_symbols += [s0, s1]
      if 'quote_ref_symbol' in product:
        s2 = product['quote_ref_symbol'].split(':')[-1]
        ref_symbols += [s2]
      trade_symbols.append(s1)
    elif 'model_smm' in strategy_name or 'model_portfolio' in strategy_name:
      s = product['trade_symbol'].split(':')[-1]
      trade_symbols.append(s)
      ref_symbols.append(s)
      ref_symbols.append(product['ref_symbol'].split(':')[-1].split('.')[0])
      ref_symbols.append(product['ref_symbol'].split(':')[-1])
  return ref_symbols, trade_symbols


def parse_config(config, strategy_name):
  target_pos = {}
  lot_size = {}
  keys_to_remove = []
  for k, product in config['products'].items():
    if 'basis_spread' in strategy_name \
        or 'mm_currency' in strategy_name \
        or 'delta_mgr' in strategy_name \
        or 'mm_portfolio' in strategy_name:
      p0, p1 = product, product['hedge']
      symbol0 = p0.get('symbol', p0.get('trade_symbol')).split(':')[-1]
      symbol1 = p1['symbol'].split(':')[-1]
      if not re.search(FLAGS.symbol, symbol0) and not re.search(FLAGS.symbol, symbol1):
        continue
      lot_size[symbol0] = p0['lot_size']
      lot_size[symbol1] = p0['lot_size']
      if FLAGS.mode == 'clear':
        target_pos[symbol0] = 0
        target_pos[symbol1] = 0
        keys_to_remove.append(k)
      elif FLAGS.mode == 'reserve':
        reserve0 = (p0['max_pos'] + p0['min_pos']) / 2
        reserve1 = 0 if 'Futures' in p1['symbol'] else (p1['max_pos'] + p1['min_pos']) / 2
        target_pos[symbol0] = reserve0
        target_pos[symbol1] = reserve1
    else:
      symbol = product['trade_symbol'].split(':')[-1]
      if not re.search(FLAGS.symbol, symbol):
        continue
      if 'model_smm' in strategy_name or 'model_portfolio' in strategy_name:
        product = product['execution']
      lot_size[symbol] = product['lot_size']
      if FLAGS.mode == 'clear':
        target_pos[symbol] = 0
        keys_to_remove.append(k)
      elif FLAGS.mode == 'reserve':
        target_pos[symbol] = (product['max_pos'] + product['min_pos']) / 2
  for k in keys_to_remove:
    del config['products'][k]
  return target_pos, lot_size


def gen_cmd(driver, config, other_params):
  mea_recipe = {}
  strategy_name = driver['strategy']['strategy_name']
  target_pos, lot_size = parse_config(config, strategy_name)
  cmds = []
  # respect the first mear in feed config Future.Binance.v1.realtime > Future.Binance.v1.index
  for key, feed in sorted(driver['feed'].items(), reverse=True):
    feed_mea = feed['mea']
    mea_recipe[feed_mea] = f'{feed_mea}.{feed["recipe"]}'
  ref_symbols, trade_symbols = get_remaining_symbols(config, strategy_name)
  for _, order in driver['order'].items():
    mea = order['mea']
    key_path = order['connection_config']['key_filepath']
    removed_keys = []
    for key, feed in driver['feed'].items():
      feed['products'] = [p for p in feed['products'] if p in ref_symbols]
      if len(feed['products']) == 0:
        removed_keys.append(key)
    for key in removed_keys:
      del driver['feed'][key]
    symbol_prices = read_binance_prices()
    for symbol in order['products']:
      if re.search(FLAGS.symbol, symbol):
        stickness = calculate_stickness(symbol, mea, symbol_prices)
        cmd = f'GLOG_logtostderr=1 {FLAGS.binary} ' \
              f'--mea={mea_recipe[mea]} ' \
              f'--symbol={symbol} ' \
              f'--key_path={key_path} ' \
              f'--target_position={target_pos[symbol]} ' \
              f'--lot_size={lot_size[symbol]} ' \
              f'{other_params}'
        if 'pass_bp' not in other_params:
          cmd += ' --pass_bp=2'
        if 'stickiness' not in other_params and stickness:
          cmd += f' --stickiness={stickness}'

        if FLAGS.mode == 'clear' and (mea.startswith('Futures.Huobi') or
                                      mea.startswith('Futures.Okex') or
                                      mea.startswith('Futures.Bybit')):
          cmd += ' --hard_close'

        if mea.startswith('Futures.Huobi.v1-linear-swap') or mea.startswith('Futures.Okex.v5-swap'):
          cmd += ' --cross_mode'

        if 'Spot' in mea and 'tick_qty' not in other_params:
          base = symbol.split('-')[0]
          tsymbol = f"{base}USDT"
          if tsymbol not in symbol_prices:
            tick_qty = FLAGS.dollar_tick_size / query_exchange_rates([base], 'USD')[0]
          else:
            tick_qty = FLAGS.dollar_tick_size / symbol_prices[tsymbol]
          market, exchange, api = mea.split('.')
          product = generate_product_from_str2(market, exchange, api, symbol)
          product_tick_qty = get_holder_from_product(product).product_info.min_qty
          tick_qty = max(tick_qty, product_tick_qty)
          cmd += f" --tick_qty={tick_qty}"

        if FLAGS.mode == 'clear' and FLAGS.cooldown_days > 0:
          write_decomm_info(mea, symbol)
        cmds.append(cmd)
    order['products'] = [p for p in order['products'] if p in trade_symbols]
  if FLAGS.fab_ver:
    return convert_to_fab(cmds)
  return " &\n".join(cmds) + " & \nwait"


def convert_to_fab(cmds):
  new_cmds = []
  strat_name = FLAGS.driver.split('/')[-1].replace('.json', '')
  for cmd in cmds:
    cmd = cmd.replace('\n', '').replace('GLOG_logtostderr=1', '').replace('=', ' ')
    cmd = f'./fab.sh -f basis_strat2/fabfile.py -P clear_position:{strat_name},"{cmd}"'
    new_cmds.append(cmd)
  return '\n'.join(new_cmds)


def main(argv):
  assert FLAGS.driver
  assert FLAGS.mode in ['clear', 'reserve']
  if not FLAGS.driver.endswith('.json') and not FLAGS.driver.startswith('python'):
    # assume it's strategy name
    FLAGS.driver = f'python/coin_deploy/basis_strat2/driver/{FLAGS.driver}.json'
  with open(FLAGS.driver) as f:
    driver = json.loads(f.read())
  config_path = FLAGS.driver.replace('/driver/', '/config/')
  with open(config_path) as f:
    config = json.loads(f.read())
  other_params = " " + " ".join(argv[1:])
  print(gen_cmd(driver, config, other_params))
  if FLAGS.write:
    with open(FLAGS.driver, 'w') as f:
      json.dump(driver, f, indent=2)
    with open(config_path, 'w') as f:
      json.dump(config, f, indent=2)


if __name__ == '__main__':
  flags.DEFINE_string(
      'binary', '../coin_binary/coin2-bazel-bin-3.10/'
      'cc/appcoin2/support/approach_target_position', '')
  flags.DEFINE_string('driver', '', 'driver_path')
  flags.DEFINE_string('symbol', '', 'symbol regex')
  flags.DEFINE_string('mode', 'clear', 'clear or reserve')
  flags.DEFINE_float('dollar_tick_size', 15, '')
  flags.DEFINE_bool('write', False, 'clear config')
  flags.DEFINE_integer('cooldown_days', 0, 'dont search new for x days')
  flags.DEFINE_bool('fab_ver', False, 'use fab verion command')

  app.run(main)
